/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef ARKCORECORE_ITEM_H
#define ARKCORECORE_ITEM_H

#include "Common.h"
#include "Object.h"
#include "LootMgr.h"
#include "ItemPrototype.h"
#include "DatabaseEnv.h"

struct SpellEntry;
class Bag;
class Unit;

struct ItemSetEffect {
	uint32 setid;
	uint32 item_count;
	SpellEntry const *spells[8];
};

enum InventoryChangeFailure {
	EQUIP_ERR_OK = 0,
	EQUIP_ERR_CANT_EQUIP_LEVEL_I = 1,
	EQUIP_ERR_CANT_EQUIP_SKILL = 2,
	EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT = 3,
	EQUIP_ERR_BAG_FULL = 4,
	EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG = 5,
	EQUIP_ERR_CANT_TRADE_EQUIP_BAGS = 6,
	EQUIP_ERR_ONLY_AMMO_CAN_GO_HERE = 7,
	EQUIP_ERR_NO_REQUIRED_PROFICIENCY = 8,
	EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE = 9,
	EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM = 10,
	EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM2 = 11,
	EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE2 = 12,
	EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED = 13,
	EQUIP_ERR_CANT_DUAL_WIELD = 14,
	EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG = 15,
	EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2 = 16,
	EQUIP_ERR_CANT_CARRY_MORE_OF_THIS = 17,
	EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE3 = 18,
	EQUIP_ERR_ITEM_CANT_STACK = 19,
	EQUIP_ERR_ITEM_CANT_BE_EQUIPPED = 20,
	EQUIP_ERR_ITEMS_CANT_BE_SWAPPED = 21,
	EQUIP_ERR_SLOT_IS_EMPTY = 22,
	EQUIP_ERR_ITEM_NOT_FOUND = 23,
	EQUIP_ERR_CANT_DROP_SOULBOUND = 24,
	EQUIP_ERR_OUT_OF_RANGE = 25,
	EQUIP_ERR_TRIED_TO_SPLIT_MORE_THAN_COUNT = 26,
	EQUIP_ERR_COULDNT_SPLIT_ITEMS = 27,
	EQUIP_ERR_MISSING_REAGENT = 28,
	EQUIP_ERR_NOT_ENOUGH_MONEY = 29,
	EQUIP_ERR_NOT_A_BAG = 30,
	EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS = 31,
	EQUIP_ERR_DONT_OWN_THAT_ITEM = 32,
	EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER = 33,
	EQUIP_ERR_MUST_PURCHASE_THAT_BAG_SLOT = 34,
	EQUIP_ERR_TOO_FAR_AWAY_FROM_BANK = 35,
	EQUIP_ERR_ITEM_LOCKED = 36,
	EQUIP_ERR_YOU_ARE_STUNNED = 37,
	EQUIP_ERR_YOU_ARE_DEAD = 38,
	EQUIP_ERR_CANT_DO_RIGHT_NOW = 39,
	EQUIP_ERR_INT_BAG_ERROR = 40,
	EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER2 = 41,
	EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH = 42,
	EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED = 43,
	EQUIP_ERR_EQUIPPED_CANT_BE_WRAPPED = 44,
	EQUIP_ERR_WRAPPED_CANT_BE_WRAPPED = 45,
	EQUIP_ERR_BOUND_CANT_BE_WRAPPED = 46,
	EQUIP_ERR_UNIQUE_CANT_BE_WRAPPED = 47,
	EQUIP_ERR_BAGS_CANT_BE_WRAPPED = 48,
	EQUIP_ERR_ALREADY_LOOTED = 49,
	EQUIP_ERR_INVENTORY_FULL = 50,
	EQUIP_ERR_BANK_FULL = 51,
	EQUIP_ERR_ITEM_IS_CURRENTLY_SOLD_OUT = 52,
	EQUIP_ERR_BAG_FULL3 = 53,
	EQUIP_ERR_ITEM_NOT_FOUND2 = 54,
	EQUIP_ERR_ITEM_CANT_STACK2 = 55,
	EQUIP_ERR_BAG_FULL4 = 56,
	EQUIP_ERR_ITEM_SOLD_OUT = 57,
	EQUIP_ERR_OBJECT_IS_BUSY = 58,
	EQUIP_ERR_NONE = 59,
	EQUIP_ERR_NOT_IN_COMBAT = 60,
	EQUIP_ERR_NOT_WHILE_DISARMED = 61,
	EQUIP_ERR_BAG_FULL6 = 62,
	EQUIP_ERR_CANT_EQUIP_RANK = 63,
	EQUIP_ERR_CANT_EQUIP_REPUTATION = 64,
	EQUIP_ERR_TOO_MANY_SPECIAL_BAGS = 65,
	EQUIP_ERR_LOOT_CANT_LOOT_THAT_NOW = 66,
	EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE = 67,
	EQUIP_ERR_VENDOR_MISSING_TURNINS = 68,
	EQUIP_ERR_NOT_ENOUGH_HONOR_POINTS = 69,
	EQUIP_ERR_NOT_ENOUGH_ARENA_POINTS = 70,
	EQUIP_ERR_ITEM_MAX_COUNT_SOCKETED = 71,
	EQUIP_ERR_MAIL_BOUND_ITEM = 72,
	EQUIP_ERR_NO_SPLIT_WHILE_PROSPECTING = 73,
	EQUIP_ERR_ITEM_MAX_COUNT_EQUIPPED_SOCKETED = 75,
	EQUIP_ERR_ITEM_UNIQUE_EQUIPPABLE_SOCKETED = 76,
	EQUIP_ERR_TOO_MUCH_GOLD = 77,
	EQUIP_ERR_NOT_DURING_ARENA_MATCH = 78,
	EQUIP_ERR_CANNOT_TRADE_THAT = 79,
	EQUIP_ERR_PERSONAL_ARENA_RATING_TOO_LOW = 80,
	EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM = 81,
	EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS = 82,
	// no output                                 = 83,
	EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED = 84,
	EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_SOCKETED_EXCEEDED = 85,
	EQUIP_ERR_SCALING_STAT_ITEM_LEVEL_EXCEEDED = 86,
	EQUIP_ERR_PURCHASE_LEVEL_TOO_LOW = 87,
	EQUIP_ERR_CANT_EQUIP_NEED_TALENT = 88,
	EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED = 89
};

enum BuyFailure {
	BUY_ERR_CANT_FIND_ITEM = 0,
	BUY_ERR_ITEM_ALREADY_SOLD = 1,
	BUY_ERR_NOT_ENOUGHT_MONEY = 2,
	BUY_ERR_SELLER_DONT_LIKE_YOU = 4,
	BUY_ERR_DISTANCE_TOO_FAR = 5,
	BUY_ERR_ITEM_SOLD_OUT = 7,
	BUY_ERR_CANT_CARRY_MORE = 8,
	BUY_ERR_RANK_REQUIRE = 11,
	BUY_ERR_REPUTATION_REQUIRE = 12
};

enum SellFailure {
	SELL_ERR_CANT_FIND_ITEM = 1, SELL_ERR_CANT_SELL_ITEM = 2, // merchant doesn't like that item
	SELL_ERR_CANT_FIND_VENDOR = 3, // merchant doesn't like you
	SELL_ERR_YOU_DONT_OWN_THAT_ITEM = 4, // you don't own that item
	SELL_ERR_UNK = 5, // nothing appears...
	SELL_ERR_ONLY_EMPTY_BAG = 6
// can only do with empty bags
};

// -1 from client enchantment slot number
enum EnchantmentSlot {
	PERM_ENCHANTMENT_SLOT = 0, // ITEM_FIELD_ENCHANTMENT_1_1
	TEMP_ENCHANTMENT_SLOT = 1, // ITEM_FIELD_ENCHANTMENT_2_1
	SOCK_ENCHANTMENT_SLOT = 2, // ITEM_FIELD_ENCHANTMENT_3_1
	SOCK_ENCHANTMENT_SLOT_2 = 3, // ITEM_FIELD_ENCHANTMENT_4_1
	SOCK_ENCHANTMENT_SLOT_3 = 4, // ITEM_FIELD_ENCHANTMENT_5_1
	BONUS_ENCHANTMENT_SLOT = 5, // ITEM_FIELD_ENCHANTMENT_6_1
	PRISMATIC_ENCHANTMENT_SLOT = 6, // ITEM_FIELD_ENCHANTMENT_7_1  added at apply special permanent enchantment
	MAX_INSPECTED_ENCHANTMENT_SLOT = 7,

	ENCHANTMENT_SLOT_7 = 7, // ITEM_FIELD_ENCHANTMENT_8_1  unknown
	REFORGE_ENCHANTMENT_SLOT = 8, // ITEM_FIELD_ENCHANTMENT_9_1  used with Reforge

	// NOTE: the following definitions are unconfirmed and the random property system is not working atm

	PROP_ENCHANTMENT_SLOT_0 = 9, // ITEM_FIELD_ENCHANTMENT_10_1 used with RandomSuffix
	PROP_ENCHANTMENT_SLOT_1 = 10, // ITEM_FIELD_ENCHANTMENT_11_1 used with RandomSuffix
	PROP_ENCHANTMENT_SLOT_2 = 11, // ITEM_FIELD_ENCHANTMENT_12_1 used with RandomSuffix and RandomProperty
	PROP_ENCHANTMENT_SLOT_3 = 12, // ITEM_FIELD_ENCHANTMENT_13_1 used with RandomProperty
	PROP_ENCHANTMENT_SLOT_4 = 13, // ITEM_FIELD_ENCHANTMENT_14_1 used with RandomProperty
	MAX_ENCHANTMENT_SLOT = 14
};

#define MAX_VISIBLE_ITEM_OFFSET       2                     // 2 fields per visible item (entry+enchantment)
#define MAX_GEM_SOCKETS               MAX_ITEM_PROTO_SOCKETS// (BONUS_ENCHANTMENT_SLOT-SOCK_ENCHANTMENT_SLOT) and item proto size, equal value expected
enum EnchantmentOffset {
	ENCHANTMENT_ID_OFFSET = 0,
	ENCHANTMENT_DURATION_OFFSET = 1,
	ENCHANTMENT_CHARGES_OFFSET = 2
// now here not only charges, but something new in wotlk
};

#define MAX_ENCHANTMENT_OFFSET    3

enum EnchantmentSlotMask {
	ENCHANTMENT_CAN_SOULBOUND = 0x01,
	ENCHANTMENT_UNK1 = 0x02,
	ENCHANTMENT_UNK2 = 0x04,
	ENCHANTMENT_UNK3 = 0x08
};

enum ItemUpdateState {
	ITEM_UNCHANGED = 0, ITEM_CHANGED = 1, ITEM_NEW = 2, ITEM_REMOVED = 3
};

enum ItemRequiredTargetType {
	ITEM_TARGET_TYPE_CREATURE = 1, ITEM_TARGET_TYPE_DEAD = 2
};

#define MAX_ITEM_REQ_TARGET_TYPE 2

#define MAX_ITEM_SPELLS 5

struct ItemRequiredTarget {
	ItemRequiredTarget(ItemRequiredTargetType uiType, uint32 uiTargetEntry) :
			m_uiType(uiType), m_uiTargetEntry(uiTargetEntry) {
	}
	ItemRequiredTargetType m_uiType;
	uint32 m_uiTargetEntry;

	// helpers
	bool IsFitToRequirements(Unit* pUnitTarget) const;
};

bool ItemCanGoIntoBag(ItemPrototype const *proto,
		ItemPrototype const *pBagProto);

class Item: public Object {
public:
	static Item* CreateItem(uint32 item, uint32 count, Player const* player =
			NULL);
	Item* CloneItem(uint32 count, Player const* player = NULL) const;

	Item();

	virtual bool Create(uint32 guidlow, uint32 itemid, Player const* owner);

	ItemPrototype const* GetProto() const;

	uint64 const& GetOwnerGUID() const {
		return GetUInt64Value(ITEM_FIELD_OWNER);
	}
	void SetOwnerGUID(uint64 guid) {
		SetUInt64Value(ITEM_FIELD_OWNER, guid);
	}
	Player* GetOwner() const;

	void SetBinding(bool val) {
		ApplyModFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_SOULBOUND, val);
	}
	bool IsSoulBound() const {
		return HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_SOULBOUND);
	}
	bool IsBoundAccountWide() const {
		return (GetProto()->Flags & ITEM_PROTO_FLAG_BIND_TO_ACCOUNT) != 0;
	}
	bool IsBindedNotWith(Player const* player) const;
	bool IsBoundByEnchant() const;
	virtual void SaveToDB(SQLTransaction& trans);
	virtual bool LoadFromDB(uint32 guid, uint64 owner_guid, Field* fields,
			uint32 entry);
	virtual void DeleteFromDB(SQLTransaction& trans);
	void DeleteFromInventoryDB(SQLTransaction& trans);
	void SaveRefundDataToDB();
	void DeleteRefundDataFromDB();

	Bag* ToBag() {
		if (IsBag())
			return reinterpret_cast<Bag*>(this);
		else
			return NULL;
	}
	const Bag* ToBag() const {
		if (IsBag())
			return reinterpret_cast<const Bag*>(this);
		else
			return NULL;
	}

	bool IsLocked() const {
		return !HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_UNLOCKED);
	}
	bool IsBag() const {
		return GetProto()->InventoryType == INVTYPE_BAG;
	}
	bool IsBroken() const {
		return GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0
				&& GetUInt32Value(ITEM_FIELD_DURABILITY) == 0;
	}
	bool CanBeTraded(bool mail = false, bool trade = false) const;
	void SetInTrade(bool b = true) {
		mb_in_trade = b;
	}
	bool IsInTrade() const {
		return mb_in_trade;
	}

	bool HasEnchantRequiredSkill(const Player *pPlayer) const;
	uint32 GetEnchantRequiredLevel() const;

	bool IsFitToSpellRequirements(SpellEntry const* spellInfo) const;
	bool IsTargetValidForItemUse(Unit* pUnitTarget);
	bool IsLimitedToAnotherMapOrZone(uint32 cur_mapId, uint32 cur_zoneId) const;
	bool GemsFitSockets() const;

	uint32 GetCount() const {
		return GetUInt32Value(ITEM_FIELD_STACK_COUNT);
	}
	void SetCount(uint32 value) {
		SetUInt32Value(ITEM_FIELD_STACK_COUNT, value);
	}
	uint32 GetMaxStackCount() const {
		return GetProto()->GetMaxStackSize();
	}
	uint8 GetGemCountWithID(uint32 GemID) const;
	uint8 GetGemCountWithLimitCategory(uint32 limitCategory) const;
	uint8 CanBeMergedPartlyWith(ItemPrototype const* proto) const;

	uint8 GetSlot() const {
		return m_slot;
	}
	Bag *GetContainer() {
		return m_container;
	}
	uint8 GetBagSlot() const;
	void SetSlot(uint8 slot) {
		m_slot = slot;
	}
	uint16 GetPos() const {
		return uint16(GetBagSlot()) << 8 | GetSlot();
	}
	void SetContainer(Bag *container) {
		m_container = container;
	}

	bool IsInBag() const {
		return m_container != NULL;
	}
	bool IsEquipped() const;

	uint32 GetSkill();
	uint32 GetSpell();

	// RandomPropertyId (signed but stored as unsigned)
	int32 GetItemRandomPropertyId() const {
		return GetInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID);
	}
	uint32 GetItemSuffixFactor() const {
		return GetUInt32Value(ITEM_FIELD_PROPERTY_SEED);
	}
	void SetItemRandomProperties(int32 randomPropId);
	void UpdateItemSuffixFactor();
	static int32 GenerateItemRandomPropertyId(uint32 item_id);
	void SetEnchantment(EnchantmentSlot slot, uint32 id, uint32 duration,
			uint32 charges);
	void SetEnchantmentDuration(EnchantmentSlot slot, uint32 duration,
			Player* owner);
	void SetEnchantmentCharges(EnchantmentSlot slot, uint32 charges);
	void ClearEnchantment(EnchantmentSlot slot);
	uint32 GetEnchantmentId(EnchantmentSlot slot) const {
		return GetUInt32Value(
				ITEM_FIELD_ENCHANTMENT_1_1 + slot * MAX_ENCHANTMENT_OFFSET
						+ ENCHANTMENT_ID_OFFSET);
	}
	uint32 GetEnchantmentDuration(EnchantmentSlot slot) const {
		return GetUInt32Value(
				ITEM_FIELD_ENCHANTMENT_1_1 + slot * MAX_ENCHANTMENT_OFFSET
						+ ENCHANTMENT_DURATION_OFFSET);
	}
	uint32 GetEnchantmentCharges(EnchantmentSlot slot) const {
		return GetUInt32Value(
				ITEM_FIELD_ENCHANTMENT_1_1 + slot * MAX_ENCHANTMENT_OFFSET
						+ ENCHANTMENT_CHARGES_OFFSET);
	}

	std::string const& GetText() const {
		return m_text;
	}
	void SetText(std::string const& text) {
		m_text = text;
	}

	void SendTimeUpdate(Player* owner);
	void UpdateDuration(Player* owner, uint32 diff);

	// spell charges (signed but stored as unsigned)
	int32 GetSpellCharges(uint8 index/*0..5*/= 0) const {
		return GetInt32Value(ITEM_FIELD_SPELL_CHARGES + index);
	}
	void SetSpellCharges(uint8 index/*0..5*/, int32 value) {
		SetInt32Value(ITEM_FIELD_SPELL_CHARGES + index, value);
	}

	Loot loot;
	bool m_lootGenerated;

	// Update States
	ItemUpdateState GetState() const {
		return uState;
	}
	void SetState(ItemUpdateState state, Player *forplayer = NULL);
	void AddToUpdateQueueOf(Player *player);
	void RemoveFromUpdateQueueOf(Player *player);
	bool IsInUpdateQueue() const {
		return uQueuePos != -1;
	}
	uint16 GetQueuePos() const {
		return uQueuePos;
	}
	void FSetState(ItemUpdateState state) // forced
			{
		uState = state;
	}

	bool hasQuest(uint32 quest_id) const {
		return GetProto()->StartQuest == quest_id;
	}
	bool hasInvolvedQuest(uint32 /*quest_id*/) const {
		return false;
	}
	bool IsPotion() const {
		return GetProto()->IsPotion();
	}
	bool IsWeaponVellum() const {
		return GetProto()->IsWeaponVellum();
	}
	bool IsArmorVellum() const {
		return GetProto()->IsArmorVellum();
	}
	bool IsConjuredConsumable() const {
		return GetProto()->IsConjuredConsumable();
	}

	// Item Refund system
	void SetNotRefundable(Player *owner, bool changestate = true);
	void SetRefundRecipient(uint32 pGuidLow) {
		m_refundRecipient = pGuidLow;
	}
	void SetPaidMoney(uint32 money) {
		m_paidMoney = money;
	}
	void SetPaidExtendedCost(uint32 iece) {
		m_paidExtendedCost = iece;
	}
	uint32 GetRefundRecipient() {
		return m_refundRecipient;
	}
	uint32 GetPaidMoney() {
		return m_paidMoney;
	}
	uint32 GetPaidExtendedCost() {
		return m_paidExtendedCost;
	}

	void UpdatePlayedTime(Player *owner);
	uint32 GetPlayedTime();
	bool IsRefundExpired();

	// Soulbound trade system
	void SetSoulboundTradeable(AllowedLooterSet* allowedLooters,
			Player* currentOwner, bool apply);
	bool CheckSoulboundTradeExpire();

	void BuildUpdate(UpdateDataMapType&);

	uint32 GetScriptId() const {
		return GetProto()->ScriptId;
	}
private:
	std::string m_text;
	uint8 m_slot;
	Bag *m_container;
	ItemUpdateState uState;
	int16 uQueuePos;
	bool mb_in_trade; // true if item is currently in trade-window
	time_t m_lastPlayedTimeUpdate;
	uint32 m_refundRecipient;
	uint32 m_paidMoney;
	uint32 m_paidExtendedCost;
	AllowedLooterSet allowedGUIDs;
};
#endif
